package cwinsor.com.leaf_raking;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Vector;

import javax.swing.JTable;
import javax.swing.event.ChangeEvent;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableColumnModel;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;


public abstract class TableModelData extends TableModelBase  {

	private static final long serialVersionUID = 1L;
	private PrsnList personList;
	TableModelData otherTable;

	private int columnCount;

	/*
	 * the columns are:
	 *   First Name
	 *   Last Name
	 *   Availability Current [n] - from .csv table
	 *   Availability Total Change [1] - computed, reduction
	 *   Assignment Current [n] - from .csv table
	 *   Total Happiness Current [1] - computed, reduction
	 *   Assignment AddDrop [n] - local interactive
	 *   Assignment Proposed [n] - computed
	 *   Total HappinessProposed [1] - computed, reduction
	 */
	public int colFirstName;
	public int colLastName;
	public int colPower;
	private int colAvailLastBase;
	private int colAvailLastTop;
	private int colAvailCurrentBase;
	private int colAvailCurrentTop;
	public int colAvailTotalChange;
	private int colAssignmentCurrentBase;
	private int colAssignmentCurrentTop;
	public int colTotalShiftsCurrent;
	public int colTotalHappinessCurrent;
	private int colAssignmentAddDropBase;
	private int colAssignmentAddDropTop;
	private int colAssignmentProposedBase;
	private int colAssignmentProposedTop;
	public int colTotalShiftsProposed;
	public int colTotalHappinessProposed;




	/**
	 * constructor
	 */
	public TableModelData(
			PrsnList personList,
			TableModelData otherTable) { 

		this.personList = personList;
		this.otherTable = otherTable;

		Msg.note(dS + ": constructor start");

		// establish column count
		columnCount =  (5*WorkShift.size()) + 8;

		int col = 0;

		addColumn("FirstName");
		colFirstName = col++;

		addColumn("Power");
		colPower = col++;

		addColumn("LastName");
		colLastName = col++;

		colAvailLastBase = col;
		for (int i=0; i<WorkShift.size(); i++) {
			addColumn("LastAvail");
			colAvailLastTop = col++;
		}	

		colAvailCurrentBase = col;
		for (int i=0; i<WorkShift.size(); i++) {
			addColumn("CurrAvail");
			colAvailCurrentTop = col++;
		}		

		addColumn("Change");
		colAvailTotalChange = col++;

		colAssignmentCurrentBase = col;
		for (int i=0; i<WorkShift.size(); i++) {
			addColumn("Team");
			colAssignmentCurrentTop = col++;
		}	

		colTotalShiftsCurrent = col++;
		addColumn("ShiftsCurrent");

		colTotalHappinessCurrent = col++;
		addColumn("HappinessCurrent");

		colAssignmentAddDropBase = col;
		for (int i=0; i<WorkShift.size(); i++) {
			addColumn("AddDrop");
			colAssignmentAddDropTop = col++;
		}	

		colAssignmentProposedBase = col;
		for (int i=0; i<WorkShift.size(); i++) {
			addColumn("T");
			colAssignmentProposedTop = col++;
		}	

		colTotalShiftsProposed = col++;
		addColumn("ShiftsProposed");

		colTotalHappinessProposed = col++;
		addColumn("HappinessProposed");

		if (col != columnCount) {
			Msg.fatal(dS + ": during construction column count mismatch expected " + columnCount + " observed " + col);
		}
		Msg.note(dS + ": constructor done");
		Msg.note(dS + "zona" + colTotalShiftsCurrent);
		Msg.note(dS + "zona" + colTotalShiftsProposed);

	}



	@Override
	public int getRowCount() {
		if (personList == null) {
			return 0;
		}
		return personList.getCount();
	}

	@Override
	public int getColumnCount() {
		return columnCount;
	}

	@Override
	public boolean isCellEditable(int row, int col) {
		if (colIsAssignmentAddDrop(col)) {
			return true;
		}
		return false;
	}

	@Override
	public void setValueAt(Object value, int row, int col) {
		// only the add/drop is settable, ignore everything else
		if (colIsAssignmentAddDrop(col)) {
			// set the add/drop
			WorkShift workShift = this.colToShift(col);
			TeamChange teamChange = TeamChange.objectToTeamChange(value);			
			setAddDrop(row, workShift, teamChange);
		}
	}

	/* 
	 * getValueAt
	 */
	public Object getValueAt(int row, int col) {

		Person person = personList.getPerson(row);

		// first name, last name
		if (colIsFirstName(col)) { return person.getFirstName(); }
		if (colIsLastName(col)) { return person.getLastName(); }
		if (colIsPower(col)) { return person.getPower(); }

		// current availability - from .csv file
		if (colIsAvailLast(col)) {
			WorkShift workShift = colToShift(col);
			return person.getLastAvailability(workShift);
		}

		// current availability - from .csv file
		if (colIsAvailCurrent(col)) {
			WorkShift workShift = colToShift(col);
			return person.getCurrentAvailability(workShift);
		}

		// total change - computed
		if (colIsAvailTotalChange(col)) {
			Integer totalChange = new Integer(0);
			for (WorkShift workShift : WorkShift.asList()) {
				totalChange = totalChange + 
						Math.abs(person.getCurrentAvailability(workShift) -
								person.getLastAvailability(workShift));
			}
			return totalChange;
		}

		// current assignment
		if (colIsAssignmentCurrent(col)) {
			WorkShift workShift = colToShift(col);
			return showIntegerAsString(getAssignmentCurrent(row,workShift).toInteger());
		}

		// total shifts
		if (colIsTotalShiftsCurrent(col)) {
			return getTotalShifts(row,true);
		}

		// total happiness
		if (colIsTotalHappinessCurrent(col)) {
			return getTotalHappiness(row,true);
		}

		// add/drop - locally held
		if (colIsAssignmentAddDrop(col)) {
			//	Msg.note(dS + ": getValueAt with colIsAssignmentAddDrop");
			WorkShift workShift = colToShift(col);
			return getAddDrop(row, workShift).toString();
		}

		// proposed assignment
		if (colIsAssignmentProposed(col)) {
			WorkShift workShift = colToShift(col);
			String rtn = showIntegerAsString(getAssignmentProposed(row, workShift).toInteger());

			// add an asterisk for rowers who have parent supervising that shift
			// or vice-versa - for parents who have a rower on that shift
			if (otherTable != null) {

				// my last name, my team
				String nameThisRow = person.getLastName();
				TeamNumber teamThisRow = getAssignmentProposed(row,workShift);

				// walk through the other table (each row - get Person)
				// if the person has the same last name, and is assigned the same shift - that is a match
				for (int i=0; i<otherTable.getRowCount();i++) {
					Person otherPerson = otherTable.personList.getPerson(i);
					String theirLastName = otherPerson.getLastName();
					TeamNumber theirTeam = otherTable.getAssignmentProposed(i, workShift);

					if (
							nameThisRow.equals(theirLastName) &&
							teamThisRow.equals(theirTeam) &&
							!teamThisRow.equals(TeamNumber.TEAM_UNASSIGNED)) {
						rtn = rtn + "S";	
						// rtn = rtn + otherPerson.getFirstName() + otherPerson.getLastName();
					}
				}
			}

			return rtn;
		}

		// total shifts as proposed
		if (colIsTotalShiftsProposed(col)) {
			return getTotalShifts(row,false);
		}

		// total happiness as proposed
		if (colIsTotalHappinessProposed(col)) {
			return getTotalHappiness(row,false);
		}
		Msg.fatal(dS + ": in getValueAt - hit the default case");
		return null;
	}

	/*
	 * get column class (for purposes of sorting)
	 */
	@Override
	public Class getColumnClass(int col) {

		if (colIsFirstName(col)) { return String.class; }
		if (colIsLastName(col)) { return String.class; }
		if (colIsPower(col)) { return Integer.class; }
		if (colIsAvailLast(col)) { return Integer.class; }
		if (colIsAvailCurrent(col)) { return Integer.class; }
		if (colIsAvailTotalChange(col)) { return Integer.class; }
		if (colIsAssignmentCurrent(col)) { return String.class; }
		if (colIsTotalShiftsCurrent(col)) { return Integer.class; }
		if (colIsTotalHappinessCurrent(col)) { return Integer.class; }
		if (colIsAssignmentAddDrop(col)) { return String.class; }
		if (colIsAssignmentProposed(col)) { return String.class; }
		if (colIsTotalShiftsProposed(col)) { return Integer.class; }
		if (colIsTotalHappinessProposed(col)) { return Integer.class; }
		Msg.fatal(dS + ": in getValueAt - hit the default case");
		return null;
	}

	// Compute total shifts for user
	public Integer getTotalShifts(Integer row, Boolean current) {
		Person person = personList.getPerson(row);
		Integer totalShifts = 0;
		for (WorkShift s : WorkShift.asList()) {
			if (
					(current) ?
							(person.getAssignmentCurrent(s) != TeamNumber.TEAM_UNASSIGNED) :
								(getAssignmentProposed(row, s) != TeamNumber.TEAM_UNASSIGNED)) {

				totalShifts += 1;

			}
		}
		return totalShifts;	
	}


	// Compute total happiness (as currently assigned or on proposed assignments)
	// computed as:
	// if assigned to that day
	//   if desired (currentAvailability > 0) add 1 to happiness
	//   if not desired (currentAvailability < 0) deduct the currentAvailability amount
	// so for example
	//   if assigned to a day where currentAvailability = -2 it would deduct 2 from happiness
	//   if assigned to a where currentAvailability = +2 it would add 1 to happiness 
	//
	public Integer getTotalHappiness(Integer row, Boolean current) {
		Person person = personList.getPerson(row);
		Integer totalHappiness = 0;
		for (WorkShift s : WorkShift.asList()) {
			if (
					(current) ?
							(person.getAssignmentCurrent(s) != TeamNumber.TEAM_UNASSIGNED) :
								(getAssignmentProposed(row, s) != TeamNumber.TEAM_UNASSIGNED)) {

				if (person.getCurrentAvailability(s) > 0) {
					totalHappiness += 1;
				}
				else {
					totalHappiness += person.getCurrentAvailability(s);
				}
			}
		}
		return totalHappiness;	
	}

	/*
	 * getTotalHappiness
	 */
	public Integer reductionTotalHappinessCurrent() {
		Integer sum = new Integer(0);
		for (int row=0; row<personList.getCount(); row++) {
			sum += getTotalHappiness(row,true);
		}
		return sum;
	}

	/*
	 * reductionTotalHappiness
	 */
	public Integer reductionTotalHappinessProposed() {
		Integer sum = new Integer(0);
		for (int row=0; row<personList.getCount(); row++) {
			sum += getTotalHappiness(row,false);
		}
		return sum;
	}

	/*
	 * reductionAssignmentCurrentcount
	 */
	public Integer reductionAssignmentCurrentCount(WorkShift workShift, TeamNumber teamNumber) {
		Integer count = new Integer(0);
		for (int row=0; row<personList.getCount(); row++) {
			Person person = personList.getPerson(row);
			if (person.getAssignmentCurrent(workShift).equals(teamNumber)) {
				count++;	
			}
		}
		return count;
	}

	/*
	 * reductionAssignmentProposedCount
	 */
	public Integer reductionAssignmentProposedCount(WorkShift workShift, TeamNumber teamNumber) {
		Integer count = new Integer(0);
		for (int row=0; row<personList.getCount(); row++) {
			if (getAssignmentProposed(row, workShift).equals(teamNumber)) {
				count++;	
			}
		}
		return count;
	}


	private TeamNumber getAssignmentCurrent(Integer row, WorkShift workShift) {
		Person person = personList.getPerson(row);
		return person.getAssignmentCurrent(workShift);
	}


	/*
	 * getAddDrop
	 */
	private String getAddDrop(Integer row, WorkShift workShift) {
		return getAddDropTc(row,workShift).toString();
	}
	private TeamChange getAddDropTc(Integer row, WorkShift workShift) {
		Person person = personList.getPerson(row);
		return person.getAddDrop(workShift);
	}



	/*
	 * setAddDrop (using row, workshift)
	 */
	private void setAddDrop(Integer row, WorkShift workShift, TeamChange teamChange) {
		Person person = personList.getPerson(row);
		person.setAddDrop(workShift, teamChange);
	}


	/*
	 * compute proposed assignment
	 * this combines the current assignment plus add/drop to make the proposed assignment
	 */
	private TeamNumber getAssignmentProposed(Integer row, WorkShift workShift) {
		Person person = personList.getPerson(row);
		TeamChange teamChange = getAddDropTc(row, workShift);
		return person.getAssignmentCurrent(workShift).updateTeam(teamChange);
	}


	/*
	 * for purposes of making a large table easier to read
	 * where we have no assignment (team==0) we will display this
	 * as a " "
	 * this method performs conversion from Integer to String
	 * with the clause that 0 will become " "
	 */
	private String showIntegerAsString(Integer integer) {
		if (integer.equals(0)) return " ";
		return integer.toString();
	}




	private Boolean colIsFirstName(int col) {
		return col == colFirstName;
	}
	private Boolean colIsLastName(int col) {
		return col == colLastName;
	}
	private Boolean colIsPower(int col) {
		return col == colPower;
	}

	/*
	 * returns a Shift given a column (for fields which have multiple elements)
	 */
	public WorkShift colToShift(Integer col) {
		if (colIsAvailLast(col))          { return WorkShift.indexToShift(col - colAvailLastBase); }
		if (colIsAvailCurrent(col))       { return WorkShift.indexToShift(col - colAvailCurrentBase); }
		//zonaDetails		if (colIsAvailDelta(col))         { return WorkShift.indexToShift(col - colAvailDeltaBase); }
		if (colIsAssignmentCurrent(col))  { return WorkShift.indexToShift(col - colAssignmentCurrentBase); }
		if (colIsAssignmentAddDrop(col))  { return WorkShift.indexToShift(col - colAssignmentAddDropBase); }
		if (colIsAssignmentProposed(col)) { return WorkShift.indexToShift(col - colAssignmentProposedBase); }
		Msg.fatal(dS + ": when computing offset - hit the default case - col = " + col);
		return null;
	}

	/*
	 * returns Boolean indicating column is the one specified
	 */

	private Boolean colIsAvailLast(int col) {
		return (col >= colAvailLastBase) && (col <= colAvailLastTop);
	}
	private Boolean colIsAvailCurrent(int col) {
		return (col >= colAvailCurrentBase) && (col <= colAvailCurrentTop);
	}
	//zonaDetails
	//	private Boolean colIsAvailDelta(int col) {
	//		return (col >= colAvailDeltaBase) && (col <= colAvailDeltaTop);
	//	}
	public Boolean colIsAvailTotalChange(int col) {
		return col == colAvailTotalChange;
	}
	public Boolean colIsAssignmentCurrent(int col) {
		return (col >= colAssignmentCurrentBase) && (col <= colAssignmentCurrentTop);
	}
	public Boolean colIsTotalShiftsCurrent(int col) {
		return (col == colTotalShiftsCurrent);
	}
	public Boolean colIsTotalHappinessCurrent(int col) {
		return (col == colTotalHappinessCurrent);
	}
	private Boolean colIsAssignmentAddDrop(int col) {
		return (col >= colAssignmentAddDropBase) && (col <= colAssignmentAddDropTop);
	}
	public Boolean colIsAssignmentProposed(int col) {
		return (col >= colAssignmentProposedBase) && (col <= colAssignmentProposedTop);
	}
	public Boolean colIsTotalShiftsProposed(int col) {
		return col == colTotalShiftsProposed;
	}
	public Boolean colIsTotalHappinessProposed(int col) {
		return col == colTotalHappinessProposed;
	}


	/*
	 * set default column widths
	 */
	public void setColumnWidths(JTable jTable) {

		TableColumnModel cM = jTable.getColumnModel();

		cM.getColumn(colFirstName).setMinWidth(100);
		cM.getColumn(colLastName).setMinWidth(100);
		cM.getColumn(colAvailTotalChange).setMinWidth(50);
		cM.getColumn(colTotalHappinessCurrent).setMinWidth(50);
		cM.getColumn(colTotalHappinessProposed).setMinWidth(50);
	}


	// debug
	String dS = this.getClass().getSimpleName();
	Integer dL = new Integer(1);
}